<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
    <title>椭圆旋转</title>
    <style>
      /* 容器 */
      .container {
        position: relative;
        width: 350px;
        height: 500px;

        outline: 1px solid #ddd;
        margin: 0 auto;
      }

      /* 容器内的 item */
      .container .item {
        position: absolute;
        width: 50px;
        height: 80px;
        /* 椭圆运动以 item 左上角为原点计算，所以设置位移以保证椭圆在容器居中 */
        transform: translate(-50%, -50%);

        line-height: 80px;
        text-align: center;
        outline: 1px solid #ddd;
        background: #fff;
      }

      /* 这个不重要 */
      .action {
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="item">1</div>
      <div class="item">2</div>
      <div class="item">3</div>
      <div class="item">4</div>
      <div class="item">5</div>
      <div class="item">6</div>
    </div>

    <br />
    <div class="action">
      <button class="btn-left">左</button>
      <button class="btn-right">右</button>
    </div>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
    <script>
      $(() => {
        // jq 对象：容器
        const $container = $('.container');
        // jq 对象：容器内所有的 item
        const $items = $container.find('.item');

        // 容器中心点 x 坐标
        const centerPointX = $container.width() / 2;
        // 容器中心点 y 坐标
        const centerPointY = $container.height() / 2;

        // 椭圆长边
        const longSide = 120;
        // 椭圆短边
        const shortSide = 50;

        // 每个 item 的角度
        const itemDeg = 360 / $items.length;
        // 每个 item 的弧度
        const itemRadian = degToRadian(itemDeg);

        // 每个 item 的宽度
        const itemWidth = $items.width();
        // 每个 item 的高度
        const itemHeight = $items.height();

        // 运动速度
        let speed = 2;
        // 当前运动了多少角度
        let deg = 0;
        // 开关：确保每一次运动的完整，运动停止后才能进行下一次的运动
        let swAnimate = false;

        // 第一次渲染，渲染每个 item 的位置
        render();

        // 渲染，根据 deg 渲染出每个 item 的位置
        function render() {
          // 使用弧度作为运动距离，根据 deg 计算当前弧度
          const radian = degToRadian(deg);

          // 遍历并设置每个 item
          $.each($items, (index, element) => {
            // 三角函数的表达式
            const expression = itemRadian * index + radian;
            // 总比例
            const rate = (Math.cos(expression) * shortSide + centerPointY) / centerPointY;
            // 宽度缩放比例
            const widthRate = Math.max(0.1, rate);
            // 高度缩放比例
            const heightRate = Math.max(0.1, rate);

            $(element).css({
              left: Math.sin(expression) * longSide + centerPointX,
              top: Math.cos(expression) * shortSide + centerPointY,
              zIndex: Math.ceil(rate * $items.length),
              width: widthRate * itemWidth,
              height: heightRate * itemHeight,
              // 这条是保证文本垂直居中的，如果没有文本可以去掉这条
              lineHeight: heightRate * itemHeight + 'px'
            });
          });
        }

        /**
         * 根据角度计算弧度
         * @param deg {number} 角度值
         * @return {number} 弧度值
         */
        function degToRadian(deg) {
          return (deg * Math.PI) / 180;
        }

        /**
         * 动画函数
         * @param speed {number} 动画速度
         */
        function animate(speed) {
          // 动画执行完前不再执行动画
          if (swAnimate) return;

          swAnimate = true;
          // 保存当前角度
          const oldDeg = deg;
          // 使用定时器实现动画
          let timer = setInterval(() => {
            // speed 为正数就是逆时针旋转，为负数就是顺时针旋转
            deg += speed;
            if (speed >= 0 && deg - oldDeg > itemDeg) {
              // 停止逆时针旋转
              deg = oldDeg + itemDeg;
              swAnimate = false;
              clearInterval(timer);
            } else if (speed < 0 && oldDeg - deg > itemDeg) {
              // 停止顺时针旋转
              deg = oldDeg - itemDeg;
              swAnimate = false;
              clearInterval(timer);
            }
            render();
          }, 10);
        }

        // 事件：左边按钮 => 逆时针旋转
        $('.btn-left').on('click', () => animate(speed));
        // 事件：右边按钮 => 顺时针旋转
        $('.btn-right').on('click', () => animate(-speed));

        const hammer = new Hammer($container.get(0));
        // 事件：右滑 => 逆时针旋转
        hammer.on('swiperight', () => animate(speed));
        // 事件：左滑 => 顺时针旋转
        hammer.on('swipeleft', () => animate(-speed));
      });
    </script>
  </body>
</html>
